//////////////////////////////////////////////////////////////////////////
//
// This file is a part of APNS provider project.
// Copyright (c) Vladimir Gumenuk, 2013-2014.
//
// PROJECT:         APNS provider
// FILE:            Log.cpp: implementation of the logging api
// AUTHOR:          Vladimir Gumenuk
// DATE:            22-Dec-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Log.h"

#pragma comment(lib, "version.Lib")

#define EXPIRATION     1
#define DEFAULTVERBOSELEVEL 1

const TCHAR month [][10] = {_T("January"), _T("February"), _T("March"), _T("April"),
                           _T("May"), _T("June"), _T("July"), _T("August"),
                           _T("September"), _T("October"), _T("December") };

const TCHAR day [][10] = {_T("Sunday"), _T("Monday"), _T("Tuesday"), _T("Wednesday"),
                          _T("Thursday"), _T("Friday"), _T("Saturday") };

/////////////////////////////////////////////////////////////
// global variables
static CString   g_sFileName;
static HANDLE    g_hLogFile = NULL;
static DWORD     g_dwLevel = 1;
static HINSTANCE g_hInstance = NULL;
static TCHAR     g_lpstrLogName[MAX_PATH];
static BOOL      g_bEnableDbgTrace = FALSE;

////////////////////////////////////////////////////////////
// forward declaration

static inline void _cdecl log_vsntraceA( LPCSTR pszFormat, va_list args )
{
    char szBuffer[1024];
    
    DWORD dwCount = _vsnprintf( szBuffer, 1024, pszFormat, args);
    if( dwCount < 0)
        dwCount = strlen( szBuffer );
    
    DWORD dwWrite;
    WriteFile( g_hLogFile, szBuffer, dwCount, &dwWrite, NULL );
    
    if( g_bEnableDbgTrace ) 
    {
        OutputDebugStringA( szBuffer );
    }
}

static inline void _cdecl log_vsntraceW( LPCWSTR pwzFormat, va_list args )
{
    wchar_t wzBuffer[1024];
    
    DWORD dwCount = _vsnwprintf( wzBuffer, 1024, pwzFormat, args);
    if( dwCount < 0)
        dwCount = wcslen( wzBuffer );
    
    DWORD dwWrite;
    USES_CONVERSION;

    LPCSTR lpstrTmp = W2A( wzBuffer );

    WriteFile( g_hLogFile, lpstrTmp, dwCount, &dwWrite, NULL );
    
    if( g_bEnableDbgTrace )
    {
        OutputDebugStringW( wzBuffer );
    }
}

static void log_getdate( CString& sDate )
{
    SYSTEMTIME time;
    GetLocalTime( &time );
    sDate.Format( _T("%s, %s %d, %d"), month[time.wMonth-1], day[time.wDayOfWeek], time.wDay, time.wYear );
}

static void log_getfileversion( CString& sVersionString, LPCTSTR pszModule  )
{
    sVersionString.Empty();
    
    DWORD dwSize, dwVal;
    dwSize = ::GetFileVersionInfoSize( (LPTSTR)pszModule, &dwVal);
    
    if (dwSize && !dwVal)
    {
        BYTE* pFileVersionBuffer = new BYTE[dwSize];
        if( !::GetFileVersionInfo( (LPTSTR)pszModule, NULL, dwSize, (void*) pFileVersionBuffer) )
            return;
        
        LPTSTR pszInfo = NULL;
        VS_FIXEDFILEINFO* pFileInfo = NULL;
        
        struct LANGANDCODEPAGE {
            WORD wLanguage;
            WORD wCodePage;
        } *lpTranslate;
        
        DWORD dwTranslate = sizeof(LANGANDCODEPAGE);
        DWORD dwLenCount;
        
        // gettig language information
        VerQueryValue(pFileVersionBuffer, 
            _T("\\VarFileInfo\\Translation"),
            (LPVOID*)&lpTranslate,
            (UINT*) &dwLenCount);
        
        for( DWORD i=0; i < dwLenCount/sizeof(struct LANGANDCODEPAGE); i++)
        {
            CString sInfo;
            sInfo.Format( _T("\\StringFileInfo\\%04x%04x\\FileVersion"), lpTranslate[i].wLanguage, lpTranslate[i].wCodePage );
            //\\StringFileInfo\\040904B0\\FileVersion
            
            if( !::VerQueryValue(pFileVersionBuffer, sInfo.GetBuffer( sInfo.GetLength() ), (void**) &pszInfo, (UINT*) &dwSize) )
                continue;
            
            sVersionString += pszInfo;
            
            if( ::VerQueryValue(pFileVersionBuffer, _T("\\"), (void**) &pFileInfo, (UINT*) &dwSize) )
                sVersionString += pFileInfo->dwFileFlags & VS_FF_DEBUG ?  _T(" debug"): _T(" release");
            
            sInfo.Format( _T(" (lang - %04x%04x)"), lpTranslate[i].wLanguage, lpTranslate[i].wCodePage );
            sVersionString += sInfo;
            
            break;
        } //for
        
        delete[] pFileVersionBuffer;
    }
    
    if( sVersionString.IsEmpty() )
        sVersionString = _T("unknown");
}

static void log_sysdllversion( LPCTSTR pszDLL)
{
    if( !pszDLL )
        return;
    
    // getting full DLL name
    TCHAR pszFullName[MAX_PATH];
    HMODULE hDLL = LoadLibrary( pszDLL );
    if( !GetModuleFileName( hDLL, pszFullName, MAX_PATH ) )
    {
        LOG1( _T("File %s is not found\n"), pszDLL );
        return;
    }
    
    CString str;
    
    log_getfileversion( str, pszFullName );
    LOG1( _T("File %s: version "), pszFullName );
    if( str.IsEmpty() )
        LOG1( _T("unknown") );
    else
        LOG1( str );
    
    LOG1( _T("\n") );
}

static void log_putcaption( )
{
    CString str;
    
    log_getdate( str );
    LOG1( _T("Application error log started %s\n\n"), str );
    
    // getting version information
    TCHAR pszModule [MAX_PATH];
    GetModuleFileName( g_hInstance, pszModule, MAX_PATH);
    log_getfileversion( str, pszModule );
    LOG1( _T("Log module version - %s\n"), str );
    GetModuleFileName( NULL, pszModule, MAX_PATH);
    log_getfileversion( str, pszModule );
    LOG1( _T("Starting process %s: version %s\n"), pszModule, str);
    
    // getting version of some system DLLs
    log_sysdllversion( _T("msvcp60.dll") );
    log_sysdllversion( _T("msvcrt.dll") );
    log_sysdllversion( _T("wsock32.dll") );
    log_sysdllversion( _T("atl.dll") );
    log_sysdllversion( _T("admin.exe") );
}

static void log_getfilename( CString &sPath )
{
    TCHAR szString[MAX_PATH];
    sPath = _T("");
    if ( GetModuleFileName( NULL, szString, MAX_PATH ) )
    {
        LPTSTR szTmp = _tcsrchr( szString, _T('\\') );
        szTmp[1] = 0;
        sPath = szString;
    }
    
    sPath += g_lpstrLogName;
}

static void log_checklogdate()
{
    CString sfile;
    log_getfilename( sfile );
    HANDLE hFile = CreateFile( sfile, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    if( hFile == INVALID_HANDLE_VALUE )
        return;
    
    FILETIME filetime;
    GetFileTime( hFile, &filetime, NULL, NULL );
    
    // get local time and convert it to filetime
    SYSTEMTIME localtime;
    GetLocalTime( &localtime );
    FILETIME _localtime;
    SystemTimeToFileTime( &localtime,&_localtime);
    LocalFileTimeToFileTime( &_localtime, &_localtime );
    
    // compare dates
    WORD dos_filetime, dos_filedate;
    WORD dos_localtime, dos_localdate;
    FileTimeToDosDateTime( &filetime, &dos_filedate, &dos_filetime);
    FileTimeToDosDateTime( &_localtime, &dos_localdate, &dos_localtime);
    
    if( dos_localdate - dos_filedate >= EXPIRATION )
    {
        SetFileTime( hFile, &_localtime, NULL, NULL );
        CloseHandle( hFile );
        hFile = NULL;
        
        // delete log file
        DeleteFile( sfile );
    }
    
    if( hFile != NULL )
        CloseHandle( hFile );  
}

BOOL LogOpen( LPCTSTR lpstrFileName )
{
    if( g_hLogFile )
        return TRUE;
    
    if( !lpstrFileName )
    {
        GetModuleFileName( NULL, g_lpstrLogName, MAX_PATH );
        TCHAR *p = _tcsrchr( g_lpstrLogName, _T('\\') );
        if( p ) {
            *p++ = 0;
            _tcscpy( g_lpstrLogName, p );
            p = _tcsrchr( g_lpstrLogName, _T('.') );
            *(++p) = 0;
            _tcscat( p, _T("log") );
        }
    }
    else
        _tcscpy( g_lpstrLogName, lpstrFileName );
    
    log_checklogdate();
    
    g_dwLevel = DEFAULTVERBOSELEVEL;
    
    log_getfilename( g_sFileName );
    
    HANDLE hFile = CreateFile( g_sFileName, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
    
    if( hFile == INVALID_HANDLE_VALUE )
    {
        g_sFileName.Empty();
        return FALSE;
    };
    
    DWORD dw = SetFilePointer( hFile, 0, 0, FILE_END );
    
    g_hLogFile = hFile;
    
    log_putcaption();
    
    return TRUE;
}

void LogClose()
{
    if( g_hLogFile )
    {
        LOG1( _T("______________________________________________________\n\n") );
        CloseHandle( g_hLogFile );
    }
    
    g_hLogFile = NULL;
}

void LogSetLevel( int iLevel )
{
    g_dwLevel = iLevel;
}

void LogEnableDbgTrace( BOOL bEnable )
{
    g_bEnableDbgTrace = bEnable;
}

void LogPrint1( LPCTSTR lpstrFmt, ... )
{
    if( g_dwLevel < 1 )
        return;

    va_list args;
    va_start( args, lpstrFmt );

#ifdef UNICODE    
    log_vsntraceW( lpstrFmt, args );
#else
    log_vsntraceA( lpstrFmt, args );
#endif
    
    va_end( args );
}

void LogPrint2( LPCTSTR lpstrFmt, ... )
{
    if( g_dwLevel < 2 )
        return;

    va_list args;
    va_start( args, lpstrFmt );

#ifdef UNICODE    
    log_vsntraceW( lpstrFmt, args );
#else
    log_vsntraceA( lpstrFmt, args );
#endif
    
    va_end( args );
}

void LogPrint3( LPCTSTR lpstrFmt, ... )
{
    if( g_dwLevel < 3 )
        return;

    va_list args;
    va_start( args, lpstrFmt );

#ifdef UNICODE    
    log_vsntraceW( lpstrFmt, args );
#else
    log_vsntraceA( lpstrFmt, args );
#endif
    
    va_end( args );
}